/*!
//      
//      \file	k2k_StateMachine.c
//      
//      \author	Copyright 2010 Valentina Vuksic <vuksv1@bfh.ch>, Beat Liechti <liecb2@bfh.ch>
//      
//      This program is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; either version 2 of the License, or
//      (at your option) any later version.
//      
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//      
//      You should have received a copy of the GNU General Public License
//      along with this program; if not, write to the Free Software
//      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.
//
//      Abstract Data Type for state machine
//      
//      \brief		CAS-EBX-03 Projektarbeit 
//      			Valentina Vuksic (VUKSV1), Beat Liechti (LIECB2)
//      			StateMachine ADT for the k2k (Kaffee2000) application
//      \version	1.0
//      \date		2010-04-11
//      \remark		Last Modifications:
//      \remark  	v1.0, VUKSV1/LIECB2, 2010-04-11 Initial release
//
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <fcntl.h>

#include "k2k_GlobalTypes.h"
#include "k2k_GlobalDefines.h"
#include "k2k_StateMachine.h"

/* -- defines --------------------------------------------------------*/

#define COUNTERFILE			"coffeecounter"
#define CLEAN_MAXCOFFEES	4
#define DEV_COFFGRN			"/dev/c2coffgrn"
#define DEV_MILKPMP			"/dev/c2milkpmp"

/* -- static variables -----------------------------------------------*/

static BOOL isCounterDirty;

/* -- static function declarations -----------------------------------*/

static void k2k_StateMachine_SignalHandler(int sigNo, void *customData);

/* -- ADT functions --------------------------------------------------*/

/*!
//
// Creates StateMachine ADT "object"
// 
// \return pointer to the StateMachine ADT
//
*/
k2k_StateMachine* k2k_StateMachine_Create(k2k_NanoX *nanoX) 
{
	k2k_StateMachine *stateMachine = NULL;
	stateMachine = malloc(sizeof(k2k_StateMachine));
	if (stateMachine != NULL)
	{
		/*! initialize structure */		
		stateMachine->previousState = STATE_MAX;
		stateMachine->currentState = STATE_OFF;
		int i;
		for(i = 0; i < STATE_MAX; i++)
		{
			stateMachine->stateHandlers[i] = NULL;
		}
		/*! reference to Nano X ADT */
		stateMachine->nanoX = nanoX;
		/*! open device for coffeee grinder engine */
		stateMachine->fd_coffgrn = open(DEV_COFFGRN, O_WRONLY);
		stateMachine->fd_milkpmp = open(DEV_MILKPMP, O_WRONLY);
		if (stateMachine->fd_coffgrn <= 0 || stateMachine->fd_milkpmp <= 0)
		{
			stateMachine->err = k2k_STATEHANDLER_ERR_OPEN_DEVICES_FAILED;
		}
		else
		{
			/*! make sure grinder is not running */
			k2k_StateMachine_SetGrinderSpeed(stateMachine, 0);
			/*! make sure milk pump is not running */
			k2k_StateMachine_SetMilkPumpAmount(stateMachine, 0);
			/*! setup signaling to catch otherwise uncaught signals from child processes (no child zombies) */
			stateMachine->signalChld = k2k_Signal_Create(SIGCHLD, k2k_StateMachine_SignalHandler, stateMachine);
			if (stateMachine->signalChld == NULL)
			{
				stateMachine->err = k2k_STATEHANDLER_ERR_CREATE_SIGNAL_FAILED;
			}
			else
			{
				/*! enable signal handler for SIGCHLD */
				k2k_Signal_Enable(stateMachine->signalChld);
				/*! flag success */
				stateMachine->err = k2k_STATEHANDLER_ERR_SUCCESS;
			}
		}
	}
	isCounterDirty = FALSE;
	return stateMachine;
}

/*!
//
// Releases StateMachine ADT "object"
// 
// \param stateMachine is the pointer to the StateMachine structure 
// 
// \return noting
//
*/
void k2k_StateMachine_Release(k2k_StateMachine *stateMachine)
{
	if (stateMachine != NULL)
	{	
		if (stateMachine->fd_coffgrn > 0) 
		{
			/*! make sure grinder is not running */
			k2k_StateMachine_SetGrinderSpeed(stateMachine, 0);
			close(stateMachine->fd_coffgrn);
			stateMachine->fd_coffgrn = -1;
		}
		if (stateMachine->fd_milkpmp > 0) 
		{
			/*! make sure milk pump is not running */
			k2k_StateMachine_SetMilkPumpAmount(stateMachine, 0);
			close(stateMachine->fd_milkpmp);
			stateMachine->fd_milkpmp = -1;
		}
		if (stateMachine->signalChld != NULL)
		{
			/*! disable signal handler */
			k2k_Signal_Disable(stateMachine->signalChld);
			/*! release signal ADT */
			k2k_Signal_Release(stateMachine->signalChld);
			stateMachine->signalChld = NULL;
		}
		/*! release memory */
		free(stateMachine);	
	}
}

/*!
//
// Forward events to the state machine
// 
// \param stateMachine is the pointer to the StateMachine structure
// \param event describes the event
// 
// \return new state of the state machine
//
*/
k2k_State k2k_StateMachine_ProcessEvent(k2k_StateMachine *stateMachine, k2k_Event event)
{
	/*! check if current state has a state handler */
	k2k_StateHandlerFunc handlerFunc = stateMachine->stateHandlers[stateMachine->currentState];
	if (handlerFunc)
	{		
		k2k_StateHandlerData *handlerData = stateMachine->handlerData[stateMachine->currentState];		
		k2k_State newState = handlerFunc(event, stateMachine->previousState, handlerData);
		stateMachine->previousState = stateMachine->currentState;
		stateMachine->currentState = newState;
	}
	else
	{
		printf("ERROR: No state handler for state %s\n", k2k_StateMachineTypes_GetStateText(stateMachine->currentState));
	}
	return stateMachine->currentState;
}

/*!
//
// Adds a state handler
// 
// \param stateMachine is the pointer to the StateMachine structure
// \param state describes the state for which the handler is called
// \param handler is the pointer to the function to handle the state
// \param customData is the pointer to custom data (typically the corresponding ADT struct)
// 
// \return nothing
//
*/
void k2k_StateMachine_AddStateHandler(k2k_StateMachine *stateMachine, k2k_State state, k2k_StateHandlerFunc handlerFunc, k2k_StateHandlerData *handlerData)
{
	stateMachine->stateHandlers[state] = handlerFunc;
	stateMachine->handlerData[state] = handlerData;
}

/*!
//
// Set the speed of the coffee grinder 
// 
// \param speed
// 
// \return nothing
//
*/
void k2k_StateMachine_SetGrinderSpeed(k2k_StateMachine *stateMachine, int speed)
{
	write(stateMachine->fd_coffgrn, &speed, sizeof(speed));
}

/*!
//
// Set the amount pumped by the milk pump 
// 
// \param amount
// 
// \return nothing
//
*/
void k2k_StateMachine_SetMilkPumpAmount(k2k_StateMachine *stateMachine, int amount)
{
	printf("setting amount %d\n", amount);
	write(stateMachine->fd_milkpmp, &amount, sizeof(amount));
}

/*!
//
// Increment the persisting coffee counter 
// 
// \param nothing
// 
// \return nothing
//
*/
void k2k_StateMachine_CountOneCoffee(void)
{
	FILE *coffeecounterFile;

	coffeecounterFile = fopen(COUNTERFILE, "a");
	if (coffeecounterFile != NULL)
	{
		fprintf(coffeecounterFile, "1\n");	// 1 not needed, but file makes sense when looking at it
		fclose(coffeecounterFile);
		isCounterDirty = TRUE;
	}
}

/*!
//
// Checks whether cleaning is required: a certain number of coffee`s has been made. 
// 
// \param nothing
// 
// \return true if cleaning is required, false otherwise
//
*/
BOOL k2k_StateMachine_IsCleaningRequired(void)
{
	//return FALSE;
	/*! amount of coffees has only changed, if this flag has been set dirty */
	if (!isCounterDirty)
	{
		return FALSE;
	}	
	/*! count lines of the coffee counter file */
	int counter = 0;	
	/*! open coffee counter file */
	FILE *coffeecounterFile;
	coffeecounterFile = fopen(COUNTERFILE, "r");
	/*! count coffees made */
	if (coffeecounterFile != NULL)
	{
		while (!feof(coffeecounterFile))
		{
			char buffer[10];
			if (fgets(buffer, 10, coffeecounterFile) != NULL)
			{
				counter++;
			}
		}				
	}	
	/*! close coffee counter file */	
	fclose(coffeecounterFile);
	/*! return true if number of coffees exceeds threshold*/
	if (counter > CLEAN_MAXCOFFEES)
	{
		return TRUE;
	}
	return FALSE;
}

/*!
//
//  Reset the persistant coffee counter
// 
// \param nothing
// 
// \return nothing
//
*/
void k2k_StateMachine_ResetCoffeeCount(void)
{
	FILE *coffeecounterFile;
	coffeecounterFile = fopen(COUNTERFILE, "w");
	if (coffeecounterFile != NULL)
	{					
		fclose(coffeecounterFile);
	}
}

/* -- static functions -----------------------------------------------*/

/*!
//
// Signal handler to catch nonhandled SIGUSR1
// coming from orphaned audio clip play processes ("Off" during play)
//
// \param sigNo describes the signal sent by the child process
// \param customData points to custom data (typically the corresponding ADT struct)
//
// \return nothing
//
*/
static void k2k_StateMachine_SignalHandler(int sigNo, void *customData)
{	
	printf("k2k_StateMachine_SignalHandler: received 0x%08X\n", sigNo);
}

